package es.unex.meigas.core;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;

import es.unex.meigas.filters.IFilter;
import es.unex.meigas.gui.World2Device;

public abstract class DasocraticElement implements IMeigasValueChangeListener, Serializable{

	public static final double NO_DATA = MeigasValue.NO_DATA;

	public static final int PROJECT = 0;
	public static final int UNIT = 1;
	public static final int STAND = 2;
	public static final int CRUISE = 3;
	public static final int PLOT = 4;
	public static final int FIXED_RADIUS_PLOT = 5;
	public static final int CONCENTRIC_PLOT = 6;
	public static final int TREE = 7;

	public final static int FREQUENCY = 0;
	public final static int HEIGHT = 1;
	public final static int LOG_HEIGHT = 2;
	public final static int VOLUME = 3;
	public final static int VOLUME_WITHOUT_BARK = 4;
	public final static int AGE = 5;
	public final static int BASIMETRIC_AREA = 6;
	public final static int DBH = 7;

	protected ArrayList<DasocraticElement> m_Elements;
	protected String m_sName = "";
	protected String m_sNotes = "";
	protected ArrayList<Picture> m_Pictures;
	protected DasocraticElement m_Parent = null;
	protected ArrayList<IFilter> m_Filters;

	protected int m_iTreesCount;
	protected double m_dTreesByHa;
	protected double m_dTotalVolumeWithBark;
	protected double m_dTotalVolumeWithoutBark;
	protected double m_dTotalVolumeWithBarkByHa;
	protected double m_dTotalVolumeWithoutBarkByHa;
	protected double m_dMeanHeight;
	protected double m_dMeanLogHeight;
	protected double m_dMeanDominantHeight;
	protected double m_dMeanSquaredDiameter;
	protected double m_dMeanDiameter;
	protected double m_dMeanBasimetricArea;
	protected double m_dTotalBasimetricAreaByHa;
	protected double m_dMeanAge;
	protected double m_dMaxDiameter;
	protected double m_dMinDiameter;
	protected double m_dMaxHeight;
	protected double m_dMinHeight;
	protected double m_dMaxVolume;
	protected double m_dMinVolume;
	//TODO add errors

	protected boolean m_bParametersCalculated = false;
	protected boolean m_bHasChanged = false;

	protected Color m_Color;

	public abstract Rectangle2D getBoundingBox();
	public abstract void paint (Graphics g, World2Device w2d);
	public abstract double getArea(); // in ha

	public DasocraticElement(){

		m_Elements = new ArrayList();
		m_Pictures = new ArrayList();
		m_Filters = new ArrayList<IFilter>();
		m_bParametersCalculated = false;

		m_Color = Color.WHITE;

	}

	public DasocraticElement getElement(int iIndex){

		return (DasocraticElement) m_Elements.get(iIndex);

	}

	public int getElementsCount(){

		return m_Elements.size();

	}

	public void getElementsRecursive(ArrayList elements) {

		int i;

		for (i = 0; i < m_Elements.size(); i++){
			elements.add(m_Elements.get(i));
		}

		for (i = 0; i < m_Elements.size(); i++){
			((DasocraticElement)m_Elements.get(i)).getElementsRecursive(elements);
		}

	}

	public DasocraticElement addElement(DasocraticElement element){

		DasocraticElement parent = _addElement(element);

		if (parent != null){
			setHasChanged(true);
			return parent;
		}
		else{
			return null;
		}

	}

	protected abstract DasocraticElement _addElement(DasocraticElement element);

	public String getName(){

		return m_sName;

	}

	public void setName(String sName){

		if (!sName.equals(m_sName)){
			m_sName = sName;
			rootHasChanged(true);
		}

	}

	public String toString(){

		return m_sName;

	}

	public DasocraticElement getParent() {

		return m_Parent;

	}

	public void setParent(DasocraticElement parent) {

		m_Parent = parent;

	}

	public String getNotes() {

		return m_sNotes;

	}

	public void setNotes(String sNotes) {

		if (!sNotes.equals(m_sNotes)){
			m_sNotes = sNotes;
			rootHasChanged(true);
		}

	}

	public DasocraticElement getParentOfType(int iType){

		DasocraticElement parent = this;
		Class type = null;

		switch (iType){
		case DasocraticElement.PROJECT:
			type = DasocraticProject.class;
			break;
		case DasocraticElement.UNIT:
			type = AdministrativeUnit.class;
			break;
		case DasocraticElement.STAND:
			type = Stand.class;
			break;
		case DasocraticElement.CRUISE:
			type = Cruise.class;
			break;
		case DasocraticElement.PLOT:
			type = Plot.class;
			break;
		case DasocraticElement.TREE:
			type = Tree.class;
			break;
		default:
			return null;
		}

		while (!type.isInstance(parent) && parent != null){
			parent = parent.getParent();
		}

		return parent;

	}

	public void setHasChanged(boolean bHasChanged){

		if (bHasChanged){
			m_bParametersCalculated = false;
			m_bHasChanged = true;
			//checkData();
			if (m_Parent != null){
				m_Parent.setHasChanged(bHasChanged);
			}
		}
		else{
			m_bHasChanged = false;
			//checkData();
			if (m_Parent != null){
				m_Parent.setHasChanged(bHasChanged);
			}
		}

	}

	public boolean getHasChanged(){

		return m_bHasChanged;

	}

	public void rootHasChanged(boolean hasChanged){

		setHasChanged(true);
		/*DasocraticElement root = getParentOfType(PROJECT);
		if (root != null){
			root.setHasChanged(true);
		}*/

	}

	public ArrayList getPictures(){

		return m_Pictures;

	}

	public void addPicture(Picture pic){

		m_Pictures.add(pic);

	}

	public boolean removePicture(Picture pic){

		return m_Pictures.remove(pic);

	}

	public String[] getSpeciesNames() {

		int i,j;
		ArrayList list = new ArrayList();
		String[] species;
		String[] speciesArray;

		for (i = 0; i < m_Elements.size(); i++){
			species = ((DasocraticElement) m_Elements.get(i)).getSpeciesNames();
			for (j = 0; j < species.length; j++){
				if (!list.contains(species[j])){
					list.add(new String(species[j]));
				}
			}
		}

		speciesArray = new String[list.size()];
		speciesArray = (String[])list.toArray(speciesArray);

		return speciesArray;

	}

	public void removeElement(DasocraticElement element) {

		m_Elements.remove(element);
		setHasChanged(true);

	}

	public ArrayList getTrees(){

		return getTrees(new IFilter[0]);

	}

	public ArrayList getTrees(IFilter[] filters){

		int i, j;
		ArrayList trees;
		ArrayList allTrees = new ArrayList();

		for (i = 0; i < m_Elements.size(); i++){
			trees = ((DasocraticElement)m_Elements.get(i)).getTrees(filters);
			for (j = 0; j < trees.size(); j++ ){
				allTrees.add(trees.get(j));
			}
		}

		return allTrees;

	}

	public boolean hasTrees(){

		int i;
		boolean bHasTrees;

		for (i = 0; i < m_Elements.size(); i++){
			bHasTrees = ((DasocraticElement)m_Elements.get(i)).hasTrees();
			if (bHasTrees){
				return true;
			}
		}

		return false;

	}

	public double getPlotsArea(IFilter[] filters){//in ha

		int i;
		double dArea = 0;
		double dPlotArea;
		ArrayList plots = getPlots(filters);

		for (i = 0; i < plots.size(); i++){
			dPlotArea = ((Plot) plots.get(i)).getArea();
			if (dPlotArea != NO_DATA){
				dArea += dPlotArea;
			}
		}

		if (dArea == 0){
			dArea = NO_DATA;
		}

		return dArea;

	}

	public ArrayList getPlots(){

		return getPlots(new IFilter[0]);

	}

	public ArrayList getPlots(IFilter[] filters){

		int i, j;
		ArrayList plots;
		ArrayList allPlots = new ArrayList();

		for (i = 0; i < m_Elements.size(); i++){
			plots = ((DasocraticElement)m_Elements.get(i)).getPlots(filters);
			for (j = 0; j < plots.size(); j++ ){
				allPlots.add(plots.get(j));
			}
		}

		return allPlots;

	}

	public ArrayList getStands(){

		return getStands(new IFilter[0]);

	}

	public ArrayList getStands(IFilter[] filters){

		int i, j;
		ArrayList stands;
		ArrayList allStands = new ArrayList();

		for (i = 0; i < m_Elements.size(); i++){
			stands = ((DasocraticElement)m_Elements.get(i)).getStands(filters);
			for (j = 0; j < stands.size(); j++ ){
				allStands.add(stands.get(j));
			}
		}

		return allStands;

	}

	public ArrayList getStandsWithLimits(){

		int i, j;
		ArrayList stands;
		ArrayList allStands = new ArrayList();

		for (i = 0; i < m_Elements.size(); i++){
			stands = ((DasocraticElement)m_Elements.get(i)).getStandsWithLimits();
			for (j = 0; j < stands.size(); j++ ){
				allStands.add(stands.get(j));
			}
		}

		return allStands;

	}

	public void calculateParameters(){

		int i;
		double dArea;
		Tree tree;
		ArrayList trees = getTrees(getFilters());

		m_iTreesCount = trees.size();

		double [] frequency = getFrequencyDistribution("Todas", 1);

		if (frequency == null){
			m_dTotalVolumeWithBark = NO_DATA;
			m_dTotalVolumeWithoutBark = NO_DATA;
			m_dTotalVolumeWithBarkByHa = NO_DATA;
			m_dTotalVolumeWithoutBarkByHa = NO_DATA;
			m_dMeanHeight = NO_DATA;
			m_dMeanLogHeight = NO_DATA;
			m_dMeanDiameter = NO_DATA;
			m_dMeanSquaredDiameter = NO_DATA;
			m_dMeanBasimetricArea = NO_DATA;
			m_dMeanAge = NO_DATA;
			m_dTotalBasimetricAreaByHa = NO_DATA;
			m_dTreesByHa = NO_DATA;
			m_dMinDiameter = NO_DATA;
			m_dMaxDiameter = NO_DATA;
			m_dMinHeight = NO_DATA;
			m_dMaxHeight = NO_DATA;
			m_dMinVolume = NO_DATA;
			m_dMaxVolume = NO_DATA;
			return;
		}

		double [] volume = getVolumeWithBarkDistribution("Todas", 1);
		double [] volumeWithoutBark = getVolumeWithoutBarkDistribution("Todas", 1);
		double [] height = getHeightDistribution("Todas", 1);
		double [] logHeight = getLogHeightDistribution("Todas", 1);
		double [] age = getAgeDistribution("Todas", 1);
		double [] basimetricArea = getBasimetricAreaDistribution("Todas", 1);

		m_dTotalVolumeWithBark = 0;
		m_dTotalVolumeWithoutBark = 0;
		m_dTotalVolumeWithBarkByHa = 0;
		m_dTotalVolumeWithoutBarkByHa = 0;
		m_dMeanHeight = 0;
		m_dMeanLogHeight = 0;
		m_dMeanDiameter = 0;
		m_dMeanBasimetricArea = 0;
		m_dMeanAge = 0;
		m_dTotalBasimetricAreaByHa = 0;
		m_dTreesByHa = 0;
		m_dMeanSquaredDiameter = 0;

		m_dMeanDominantHeight = 0;//TODO: actualizar estos

		m_dMinDiameter = Double.MAX_VALUE;
		m_dMaxDiameter = Double.NEGATIVE_INFINITY;
		m_dMinHeight = Double.MAX_VALUE;
		m_dMaxHeight = Double.NEGATIVE_INFINITY;
		m_dMinVolume = Double.MAX_VALUE;
		m_dMaxVolume = Double.NEGATIVE_INFINITY;

		for (i = 0; i < m_iTreesCount; i++){
			tree = (Tree)trees.get(i);
			m_dMinDiameter = Math.min(m_dMinDiameter, tree.getDBH().getValue());
			m_dMinHeight = Math.min(m_dMinHeight, tree.getHeight().getValue());
			m_dMinVolume = Math.min(m_dMinVolume, tree.getVolumeWithBark().getValue());
			m_dMaxDiameter = Math.max(m_dMaxDiameter, tree.getDBH().getValue());
			m_dMaxHeight = Math.max(m_dMaxHeight, tree.getHeight().getValue());
			m_dMaxVolume = Math.max(m_dMaxVolume, tree.getVolumeWithBark().getValue());
		}

		for (i = 0; i < frequency.length; i++){
			m_dTreesByHa += frequency[i];
			m_dTotalVolumeWithBarkByHa += volume[i] * frequency[i];
			m_dTotalVolumeWithoutBarkByHa += volumeWithoutBark[i] * frequency[i];
			m_dMeanBasimetricArea = basimetricArea[i] * frequency[i];
			m_dMeanHeight += height[i] * frequency[i];
			m_dMeanLogHeight += logHeight[i] * frequency[i];
			m_dMeanDiameter += i * frequency[i];
			m_dMeanAge += age[i] * frequency[i];
			m_dMeanSquaredDiameter += (i * i * frequency[i]);
		}

		if(m_dTreesByHa !=0){
			m_dTotalBasimetricAreaByHa = m_dMeanBasimetricArea;
			m_dMeanHeight = m_dMeanHeight / m_dTreesByHa;
			m_dMeanLogHeight = m_dMeanLogHeight / m_dTreesByHa;
			m_dMeanDiameter = m_dMeanDiameter / m_dTreesByHa;
			m_dMeanBasimetricArea = m_dMeanBasimetricArea / m_dTreesByHa;
			m_dMeanAge = m_dMeanAge / m_dTreesByHa;
			m_dMeanSquaredDiameter = Math.sqrt(m_dMeanSquaredDiameter / m_dTreesByHa);
			dArea = getArea();
			if (dArea != NO_DATA){
				m_dTotalVolumeWithBark = m_dTotalVolumeWithBarkByHa * dArea;
				m_dTotalVolumeWithoutBark = m_dTotalVolumeWithoutBarkByHa* dArea;
			}
			else{
				m_dTotalVolumeWithBark = NO_DATA;
				m_dTotalVolumeWithoutBark = NO_DATA;
			}
		}
		else{
			m_dMeanHeight = NO_DATA;
			m_dMeanLogHeight = NO_DATA;
			m_dMeanDiameter = NO_DATA;
			m_dMeanBasimetricArea = NO_DATA;
			m_dMeanAge = NO_DATA;
		}

		m_bParametersCalculated = true;

	}

	public double[] getDistribution(String sSpecie, int iInterval, int iParameter){

		int i,j;
		int iClasses;
		ArrayList plots = getPlots(getFilters());
		ArrayList trees = getTrees(getFilters());
		Plot plot;
		int iPlotsCount[];

		iClasses = (int) Math.ceil(getMaximumDiameter(trees) / (double) iInterval);

		if (iClasses == 0){
			return null;
		}

		double distribution[] = new double[iClasses];
		double distributionInPlot[];
		iPlotsCount = new int[iClasses];

		for (i = 0; i < iClasses; i++){
			distribution[i] = 0;
			iPlotsCount[i] = 0;
		}

		for (i = 0; i < plots.size(); i++){
			plot = (Plot)plots.get(i);
			switch (iParameter){
			case FREQUENCY:
				distributionInPlot = plot.getFrequencyDistribution(sSpecie, iInterval);
				break;
			case HEIGHT:
				distributionInPlot = plot.getHeightDistribution(sSpecie, iInterval);
				break;
			case LOG_HEIGHT:
				distributionInPlot = plot.getLogHeightDistribution(sSpecie, iInterval);
				break;
			case VOLUME:
				distributionInPlot = plot.getVolumeWithBarkDistribution(sSpecie, iInterval);
				break;
			case VOLUME_WITHOUT_BARK:
				distributionInPlot = plot.getVolumeWithoutBarkDistribution(sSpecie, iInterval);
				break;
			case AGE:
				distributionInPlot = plot.getAgeDistribution(sSpecie, iInterval);
				break;
			case BASIMETRIC_AREA:
				distributionInPlot = plot.getBasimetricAreaDistribution(sSpecie, iInterval);
				break;
			default:
				return null;
			}
			if (distributionInPlot!= null){
				for (j = 0; j < distributionInPlot.length; j++){
					distribution[j] += distributionInPlot[j];
					if (distributionInPlot[j] != 0){
						iPlotsCount[j]++;
					}
				}
			}
		}

		for (i = 0; i < distribution.length; i++){
			if (iPlotsCount[i] != 0){
				distribution[i] /= iPlotsCount[i];
			}
		}

		return distribution;


	}

	public double[] getFrequencyDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, FREQUENCY);

	}

	public double[] getHeightDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, HEIGHT);

	}

	public double[] getLogHeightDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, LOG_HEIGHT);

	}

	public double[] getAgeDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, AGE);

	}

	public double[] getBasimetricAreaDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, BASIMETRIC_AREA);

	}

	public double[] getVolumeWithBarkDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, VOLUME);

	}

	public double[] getVolumeWithoutBarkDistribution(String sSpecie, int iInterval){

		return getDistribution(sSpecie, iInterval, VOLUME_WITHOUT_BARK);

	}

	protected double getMaximumDiameter(ArrayList trees) {

		int i;
		double dMax = 0;

		for (i = 0; i < trees.size(); i++){
			dMax = Math.max(dMax, ((Tree)trees.get(i)).getDBH().getValue());
		}

		return dMax;

	}
	public double getMeanAge() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanAge;
	}

	public double getMeanBasimetricArea() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanBasimetricArea;

	}

	public double getMeanDominantHeight() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanDominantHeight;

	}

	public double getMeanHeight() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanHeight;

	}

	public double getMeanLogHeight() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanLogHeight;

	}


	public double getMeanDiameter() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanDiameter;

	}

	public double getMeanSquaredDiameter() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMeanSquaredDiameter;

	}

	public double getTotalBasimetricAreaByHa() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dTotalBasimetricAreaByHa;

	}

	public double getTotalVolumeWithBark() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dTotalVolumeWithBark;

	}

	public double getTotalVolumeWithBarkByHa() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dTotalVolumeWithBarkByHa;

	}

	public double getTotalVolumeWithoutBark() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dTotalVolumeWithoutBark;

	}

	public double getTotalVolumeWithoutBarkByHa() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dTotalVolumeWithoutBarkByHa;

	}

	public int getTreesCount() {

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_iTreesCount;

	}

	public double getTreesByHa(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dTreesByHa;

	}

	public double getMinDiameter(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMinDiameter;

	}

	public double getMaxDiameter(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMaxDiameter;

	}

	public double getMinHeight(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMinHeight;

	}

	public double getMaxHeight(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMaxHeight;

	}

	public double getMinVolume(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMinVolume;

	}

	public double getMaxVolume(){

		if (!m_bParametersCalculated){
			calculateParameters();
		}

		return m_dMaxVolume;

	}

	public void valueHasChanged() {

		setHasChanged(true);

	}

	protected boolean checkData(){

		int i;

		for (i = 0; i < m_Elements.size(); i++){
			if (!((DasocraticElement)m_Elements.get(i)).hasValidData()){
				return false;
			}
		}

		return true;

	}

	public boolean hasValidData(){

		return checkData();

	}

	public abstract String[] getReport();

	public DasocraticElement getNewInstance(){

		DasocraticElement element = null;
		try {
			element = this.getClass().newInstance();
			element.setName(m_sName);
			element.setNotes(m_sNotes);
			for (int i = 0; i < m_Pictures.size(); i++) {
				element.addPicture(m_Pictures.get(i).getNewInstance());
			}
			for (int i = 0; i < m_Filters.size(); i++) {
				element.addFilter((IFilter) m_Filters.get(i).clone());
			}
			for (int i = 0; i < m_Elements.size(); i++) {
				element.addElement(m_Elements.get(i).getNewInstance());
			}
		} catch (Exception e){}

		return element;

	}

	public void removeFilter(IFilter filter) {

		m_Filters.remove(filter);
		m_bParametersCalculated = false;
		m_bHasChanged = false;

	}

	public void addFilter(IFilter filter){

		m_Filters.add(filter);
		m_bParametersCalculated = false;
		m_bHasChanged = false;

	}

	public IFilter[] getFilters(){

		return (IFilter[]) m_Filters.toArray(new IFilter[0]);

	}

	public FilterAndDasocraticElement[] getParentFilters() {

		ArrayList<FilterAndDasocraticElement> filtersArray = new ArrayList<FilterAndDasocraticElement>();

		DasocraticElement parent = this.getParent();

		while (parent != null){
			IFilter[] filters = parent.getFilters();
			for (int i = 0; i < filters.length; i++) {
				FilterAndDasocraticElement fade = new FilterAndDasocraticElement();
				fade.element = parent;
				fade.filter = filters[i];
				filtersArray.add(fade);
			}
			parent = parent.getParent();

		}

		return (FilterAndDasocraticElement[]) filtersArray.toArray(new FilterAndDasocraticElement[0]);

	}

	public class FilterAndDasocraticElement{

		public IFilter filter;
		public DasocraticElement element;

		public String toString(){

			return element.getName() + "-->" + filter.toString();

		}

	}


}
